
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/semaphore.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include<linux/mm.h>

#define MAJOR_NUM 123
#define MINOR_NUM 0

MODULE_AUTHOR("mdy");
MODULE_DESCRIPTION("mmap demo");

static struct class * hello_class;
static struct cdev hello_cdev;
static dev_t devnum = 0;
static char * modname = "hello_mod";
static char * devicename = "hello";
static char * classname = "hello_class";

static int open_count = 0;
static struct semaphore sem;

static int hello_mod_mmap(struct file *, struct vm_area_struct*);

struct file_operations hello_mod_fops =
{
    .owner = THIS_MODULE,
    .mmap = hello_mod_mmap
};

static int hello_mod_mmap(struct file *fp, struct vm_area_struct* vma)
{
    int area_len = 1<<20;
    void* kbuff = kmalloc(area_len, GFP_KERNEL);

    unsigned long pfn = virt_to_phys(kbuff) >> PAGE_SHIFT;
    unsigned long pfn_start = pfn + vma->vm_pgoff;
    size_t size = vma->vm_end - vma->vm_start;
    phys_addr_t offset = (phys_addr_t) vma->vm_pgoff << PAGE_SHIFT;

    printk("pgoff = %lu, offset = %llu\n", vma->vm_pgoff, offset);

    /* We cannot mmap too big areas */
    if (size + offset > area_len)
    {
	printk("size %lu or offset %llu too big!\n", size, offset);
        return -EINVAL;
    }

    /* Remap-pfn-range will mark the range VM_IO */
    if (remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot))
    {
	printk("remap_pfn_range fail!\n");
        return -EAGAIN;
    }

    printk("mmap success, vir_addr: 0x%lx, phy_addr: 0x%lx.\n", vma->vm_start, pfn_start << PAGE_SHIFT);
    return 0;
}

static int __init hello_mod_init(void)
{
    int result;
    printk("+hello_mod_init()!\n");
    devnum = MKDEV(MAJOR_NUM, MINOR_NUM);
    result = register_chrdev_region(devnum, 1, modname);

    if(result < 0)
    {
        printk("hello_mod : can't get major number!\n");
        return result;
    }

    cdev_init(&hello_cdev, &hello_mod_fops);
    hello_cdev.owner = THIS_MODULE;
    hello_cdev.ops = &hello_mod_fops;
    result = cdev_add(&hello_cdev, devnum, 1);
    if(result)
        printk("Failed at cdev_add()");
    hello_class = class_create(THIS_MODULE, classname);
    if(IS_ERR(hello_class))
    {
        printk("Failed at class_create()\n");
    }
    else
    {
        device_create(hello_class, NULL, devnum,NULL, devicename);
    }

    open_count = 0;
    sema_init(&sem, 1);
    printk("-hello_mod_init()!\n");
    return 0;
}

static void __exit hello_mod_exit(void)
{
    printk("+hello_mod_exit!\n");
    cdev_del(&hello_cdev);
    device_destroy(hello_class, devnum);
    class_destroy(hello_class);
    unregister_chrdev_region(devnum, 1);
    printk("-hello_mod_exit!\n");
    return ;
}

module_init(hello_mod_init);
module_exit(hello_mod_exit);
MODULE_LICENSE("GPL");

